//
//  BBHintedBlobDetector.m
//  BBTouch
//
//  Created by ben smith on 8/7/08.
//  This file is part of BBTouch.
//
//  BBTouch is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.

//  BBTouch is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.

//  You should have received a copy of the GNU Lesser General Public License
//  along with BBTouch.  If not, see <http://www.gnu.org/licenses/>.
// 
//  Copyright 2008 Ben Britten Smith ben@benbritten.com .
//

#import "BBHintedBlobDetector.h"
#import "BBPixelBlob.h"
#import "BBConfigurationController.h"


@implementation BBHintedBlobDetector

@synthesize thresholdMapImage;
@synthesize useHints;

- (id) init
{
	self = [super init];
	if (self != nil) {
		[[[BBConfigurationController sharedConfigurationController] configurationSettings] addObserver:self forKeyPath:@"BBMainThreshold" options:NSKeyValueObservingOptionNew context:nil];

	}
	return self;
}


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
	// we need to change our event delegate
	[self calculateThresholdMap];
}


-(void)calculateThresholdMap
{
	return;
	if (bgImage == nil) return;
	self.thresholdMapImage = [self bgImage];
	
	NSBitmapImageRep * bgBitRep = [[bgImage representations] objectAtIndex:0];
	bgBytes = [bgBitRep bitmapData];
	
	NSBitmapImageRep * bitRep = [[thresholdMapImage representations] objectAtIndex:0]; 
	
	thresholdBytes = [bitRep bitmapData];


	unsigned char max,min;
	max = 0;
	min = 0xff;
	long long totalValue = 0;
	int count = 0;
	// this will be a bit slow and lame
	for (loopY = yMin; loopY < yMax; loopY++) {
		for (loopX = xMin; loopX < xMax; loopX++) {
			int position = (loopY * bytesPerRow) + (loopX * samplesPerPixel);
			totalValue += thresholdBytes[position];
			count++;
			if (thresholdBytes[position] > max) max = thresholdBytes[position];
			if (thresholdBytes[position] < min) min = thresholdBytes[position];			
		}
	}	
	int avg = totalValue/count;
	// great, now we have the reference values, we build the map
	for (loopY = yMin; loopY < yMax; loopY++) {
		for (loopX = xMin; loopX < xMax; loopX++) {
			int position = (loopY * bytesPerRow) + (loopX * samplesPerPixel);
			// set this threshold so that it will be 'equal' to the max thresh (ie offset it so that 
			// a similarly lit pixel here will pass
			if (avg < thresholdBytes[position]) {
				//bgBytes[position] -= thresholdBytes[position] - avg;
			} else {
				bgBytes[position] += avg - thresholdBytes[position];
			}
			unsigned char diff = avg - thresholdBytes[position];
	//		NSLog(@"%d %d %d",diff,avg,thresholdBytes[position]);
			// this should be our new adjusted threshold number for this position
			thresholdBytes[position] = diff;
		}
	}	
}


-(BOOL)_validBlobPixel:(int)position
{
	// note, if you change this then you should also change scanSrc
	// they both have the check for 'goodness'
	// this double-code is for performance
	if (!useHints) {
		// we arent using the hints then defer to the parent
		return [super validBlobPixel:position];
	}
	
	if (( position < 0 ) || (position >= maxDataPosition)) {
		NSLog(@"position Out of bounds %d",position);
		return NO;
	}
	if ((srcBytes[position] - bgBytes[position]) > thresholdBytes[position]) {
		return YES;
	}
	return NO;
}


-(void)_scanSrcDataWithROI
{
	if (!useHints) {
		// we arent using the hints then defer to the parent
		[super scanSrcDataWithROI];
		return;		
	}
	// run it like a state machine, set the state variables, then call the right functions
	// nested for loops; to scan through the entire image
	labelIndex = 1; // the labeling number for our blobs
	for (loopY = yMin; loopY < yMax; loopY++) {
		for (loopX = xMin; loopX < xMax; loopX++) {
			loopXYDataPosition = (loopY * bytesPerRow) + (loopX * samplesPerPixel);
			// the vast majority of pixels will be 'bad' ones and those just need to be marked
			// and move on
			// note, if you change this then you should also change validBlobPixel:
			if ((srcBytes[loopXYDataPosition] - bgBytes[loopXYDataPosition]) <= thresholdBytes[loopXYDataPosition]) {
				// bad move on
				labels[(loopY * pixelsWide) + loopX] = -1;
				continue;
			}
			// some are 'good' pixels
			[self handleGoodPixel];
		}
	}	
}

-(void)_handleGoodPixel
{	
	// this will be handy
	int statePosition = ((loopY * pixelsWide) + loopX);
	// ok, step 1: if our current pixel is UNMARKED and the pixel above it is a non-blob.. '6' is right above
	if ((labels[statePosition] == 0) && (![self validLoopPixelAbove])) {
		// then we start a new external contour
		// this is a new blob, so make a new label
		contourLabel = labelIndex;
		if ([self externalContour]) labelIndex++;
		// dont return here, it may be that step 2 is also valid
	}
	
	// step 2: if the pixel below this one is an UNMARKED non-blob
	
	int statePositionBelow = ((loopY + 1) * pixelsWide) + loopX;
	if ((labels[statePositionBelow] == 0) && ![self validLoopPixelBelow]) {
		
		// first figure out the label for our current pixel
		if (labels[statePosition] == 0) { // if it is unmarked, then it must be the same as the pixel to the left
			labels[statePosition] = labels[statePosition - 1]; 
			// whenever we label something a blob pixel, we should add it to the appropriate blob
			if (labels[statePosition] == -1) {
				NSLog(@"BADDD2 %d %d %d",srcBytes[loopXYDataPosition],thresholdBytes[loopXYDataPosition],bgBytes[loopXYDataPosition]);
			} else {
				[[blobs objectAtIndex:labels[statePosition - 1]] addPixel:NSMakePoint(loopX, loopY) withValue:srcBytes[loopXYDataPosition]];					
			}
		}
		// now we start a new internal contour
		contourLabel = labels[statePosition]; // same as current label 
		[self internalContour];
		return; // shunt
	} 
	
	// step 3, if it isnt one of the above, and it is NOT already marked then it is a 
	// blob pixel and the label should match the one to the left
	
	if (labels[statePosition] == 0) {
		// whenever we label something a blob pixel, we should add it to the appropriate blob
		labels[statePosition] = labels[statePosition - 1];
		if (labels[statePosition] == -1) {
			NSLog(@"BADDD %d %d %d",srcBytes[loopXYDataPosition],thresholdBytes[loopXYDataPosition],bgBytes[loopXYDataPosition]);
		} else {
			[[blobs objectAtIndex:labels[statePosition - 1]] addPixel:NSMakePoint(loopX, loopY) withValue:srcBytes[loopXYDataPosition]];					
		}
	}
	
}

// finds the contour of the blob
-(BOOL)_contour
{
	// set initial conditions
	contourX = loopX;
	contourY = loopY;
	contourXYDataPosition = loopXYDataPosition;
	
	// find our first neighbor, we need this later to tell if we are done
	int contourFirstNeighbor = [self findNextNeighbor];	
	if (contourFirstNeighbor == -1) {
		// this 'blob' is just an isolated pixel
		// throw it out
		labels[(contourY * pixelsWide) + contourX] = -1;
		return NO; //shunt
	}
	
	// this is a blob then so label our start pixel, if it isnt already
	// (internal contours usually start with pixel that are already labeled
	if (labels[(contourY * pixelsWide) + contourX] == 0) {
		labels[(contourY * pixelsWide) + contourX] = contourLabel;
		if (contourLabel  == -1) NSLog(@"FUCK1");
		[[blobs objectAtIndex:contourLabel] addPixel:NSMakePoint(contourX, contourY)  withValue:srcBytes[contourXYDataPosition]];		
	}
	
	// now we have our initial points, start the iterations
	// the odd thing about this loop is the assignment of contourNeighborCheckIndex
	// it is meant to be 2 positions clockwise from the direction of the last
	// neighbor pixel.  When we come into the loop, contourNeighborCheckIndex is 'pointing'
	// toward the pixel we just moved to form the last pixel, so really we want to go
	// 2 step anti-clockwise from this position. (or 6 positions clockwise, modded)
	
	int nextNeighbor;
	BOOL done = NO;
	do {
		// move the contour to the found neighbor pixel
		[self moveContourStateToNeighbor:contourNeighborCheckIndex];
		// update the checkIndex
		contourNeighborCheckIndex = (contourNeighborCheckIndex + 6) % 8;
		// look for the next neighbor
		nextNeighbor = [self findNextNeighbor];	// this will alter contourNeighborCheckIndex
		
		// end condition: we are at the start position and the next neighbor is the same as the first one
		if ((contourX == loopX) && (contourY ==  loopY) && (nextNeighbor == contourFirstNeighbor)) {
			//then we are done
			done = YES;
		} else { // not at the start mark this pixel, if it is unmarked
			if (labels[(contourY * pixelsWide) + contourX] == 0) {
				if (contourLabel  == -1)  {
				} else {
					labels[(contourY * pixelsWide) + contourX] = contourLabel;
					[[blobs objectAtIndex:contourLabel] addPixel:NSMakePoint(contourX, contourY) withValue:srcBytes[contourXYDataPosition]];							
				}
			}
		}
	} while (!done);
	return YES;
}



-(void)setBgImage:(NSImage*)anImage
{
	[super setBgImage:anImage];
	[self calculateThresholdMap];
}

@end
